package com.panopset.flywheel;

import static com.panopset.Util.log;
import static com.panopset.Util.x;

import java.io.StringWriter;
import java.util.List;

import com.panopset.StringLineSupplier;

/**
 * Once a Flywheel Script file is read, it becomes a template to be processed.
 * Templates may also be processed using the template directive within any
 * template.
 *
 * @author Karl Dinwiddie
 */
public final class Template {

    /**
     * Every template is created by a Flywheel object.
     */
    private final Flywheel f;

    /**
     * String line supplier.
     */
    private final StringLineSupplier stringLineSupplier;

    /**
     * Source file.
     */
    private SourceFile sf;

    /**
     * First command.
     */
    private Command firstCommand;

    /**
     * Get first command.
     *
     * @return First command.
     */
    public Command getFirstCommand() {
        return firstCommand;
    }

    /**
     * Set first command.
     *
     * @param command
     *            Command to set as first command.
     */
    public void setFirstCommand(final Command command) {
        firstCommand = command;
    }

    /**
     * Current CommandFile for this template, used during execution.
     */
    private CommandFile commandFil;

    /**
     * Get command file.
     *
     * @return Command file.
     */
    public CommandFile getCommandFile() {
        return commandFil;
    }

    /**
     * Set command file.
     *
     * @param commandFile
     *            Command file.
     */
    public void setCommandFile(final CommandFile commandFile) {
        commandFil = commandFile;
    }

    /**
     * Raw commands for this template.
     */
    private List<Command> rawCommands;

    /**
     * Execute this template.
     *
     * @param stringWriter
     *            String writer.
     */
    void exec(final StringWriter stringWriter) {
        for (Command topCommand : getTopCommands()) {
            if (f.isStopped()) {
                return;
            }
            topCommand.resolve(stringWriter);
        }
    }

    /**
     * Get raw commands.
     *
     * @return Raw commands.
     */
    private List<Command> getRawCommands() {
        if (rawCommands == null) {
            rawCommands = new RawCommandLoader(this).load();
            RawCommandLoader.addStructure(rawCommands);
        }
        return rawCommands;
    }

    /**
     * Top commands.
     */
    private List<Command> topCommands;

    /**
     * Get top commands.
     *
     * @return Top commands.
     */
    public List<Command> getTopCommands() {
        if (topCommands == null) {
            try {
                List<Command> commands = new ImpliedQuitFilter()
                        .addImpliedQuits(getRawCommands());
                topCommands = CommandMatcher.matchQuitCommands(commands);
            } catch (Exception ex) {
                log(ex);
                stop(ex.getMessage());
            }
        }
        return topCommands;
    }

    /**
     * Output template results to a writer.
     *
     * @param stringWriter
     *            String writer.
     */
    public void output(final StringWriter stringWriter) {
        try {
            for (Command topCommand : getTopCommands()) {
                if (topCommand == null || f == null || f.getWriter() == null) {
                    return;
                }
                topCommand.resolveCommand(f.getWriter());
            }
        } catch (Throwable t) {
            log(t);
            stop(t.getMessage());
        }
    }

    /**
     * Called if something unexpected happened. Calls script.stop as well.
     *
     * @param msg
     *            Message.
     */
    private void stop(final String msg) {
        log(x("Stopped while processing line ") + stringLineSupplier.getLine()
                + ": " + stringLineSupplier.getFullName());
        f.stop(msg);
    }

    /**
     * Get flywheel.
     * @return Flywheel associated with this template.
     */
    public Flywheel getFlywheel() {
        return f;
    }

    /**
     * Get relative path.
     *
     * @return Relative path.
     */
    public String getRelativePath() {
        if (sf == null) {
            return "";
        }
        return sf.getRelativePath();
    }

    /**
     * Constructor.
     *
     * @param flywheel
     *            Flywheel.
     * @param sourceFile
     *            Source file.
     */
    public Template(final Flywheel flywheel, final SourceFile sourceFile) {
        f = flywheel;
        sf = sourceFile;
        stringLineSupplier = new StringLineSupplier(sourceFile.getFile());
    }

    /**
     * @param flywheel
     *            Flywheel
     * @param newStringLineSupplier
     *            String line supplier.
     */
    public Template(final Flywheel flywheel,
            final StringLineSupplier newStringLineSupplier) {
        f = flywheel;
        sf = null;
        stringLineSupplier = newStringLineSupplier;
    }

    /**
     * String line supplier.
     *
     * @return String line supplier.
     */
    public StringLineSupplier getStringLineSupplier() {
        return stringLineSupplier;
    }
}
